22.1 初始化

用于提供本地缓存对象分配的poolLocal类似内存分配器里的cache,总是和P绑定,为当前工作线程提供快速无锁分配。而Pool则管理多个P/poolLocal。

pool.go

type Pool struct { local unsafe.Pointer // [P]poolLocal 数组指针 localSize uintptr // 数组内 poolLocal 的数量 New func() interface{} // 新建对象函数 } type poolLocal struct { private interface{} // 私有缓存区 shared []interface{} // 可共享缓存区 Mutex
pad [128]byte
}

Pool用local和localSize维护一个动态poolLocal数组。无论是Get,还是Put操作都会通过pin来返回与当前P绑定的poolLocal对象,这里面就有初始化的关键。

pool.go

func (p *Pool) pin() *poolLocal { // 返回当前 P.id pid := runtime_procPin() s := atomic.LoadUintptr(&p.localSize) l := p.local // 如果 P.id 没有超出数组索引限制,则直接返回 // 这是考虑到 procresize/GOMAXPROCS 的影响 if uintptr(pid) < s { return indexLocal(l, pid) } // 没有结果时,会涉及全局加锁操作 // 比如重新分配数组内存,添加到全局列表 return p.pinSlow() }

pool.go

var ( allPoolsMu Mutex allPools []*Pool ) func (p *Pool) pinSlow() *poolLocal { // M.lock— runtime_procUnpin() // 加锁 allPoolsMu.Lock() defer allPoolsMu.Unlock() pid := runtime_procPin() // 再次检查是否符合条件,可能中途已被其他线程调用 s := p.localSize l := p.local if uintptr(pid) < s { return indexLocal(l, pid) } // 如果数组为空,新建 // 将其添加到 allPools,垃圾回收器以此获取所有 Pool 实例 if p.local == nil { allPools = append(allPools, p) } // 根据 P 数量创建 slice size := runtime.GOMAXPROCS(0) local := make([]poolLocal, size) // 将底层数组起始指针保存到 Pool.local,并设置 P.localSize atomic.StorePointer((*unsafe.Pointer)(&p.local), unsafe.Pointer(&local[0])) atomic.StoreUintptr(&p.localSize, uintptr(size)) // 返回本次所需的 poolLocal return &local[pid] }

至于indexLocal操作,完全是“聪明且偷懒”的做法。

pool.go

func indexLocal(l unsafe.Pointer, i int) poolLocal { // 不去考虑 Pool.local,也就是 l 参数实际数组的长度,反正也不会超过 1000000 // 直接将其转换成大数组,然后按索引号返回 poolLocal 即可 return &([1000000]poolLocal)(l)[i] }

不要觉得无厘头,这种做法在C里很常见,甚至你在某些操作系统的源码里也会看到类似的东西。这么做不用去考虑P数量的变化,或者对_MaxGomaxprocs的修改,直接以性能优先。